跳到主要内容

Go 实现简单的客户端服务器(CS)模式

如何实现一个简单的服务端客户端模式呢?

这里屏蔽了 Socket 连接细节,使用 Go 的服务器通常会在协程中执行向客户端的响应,故而会对每一个客户端请求启动一个协程。

一个常用的操作方法是客户端请求自身中包含一个通道,而服务器则向这个通道发送响应。

如下,一个基本的 C/S 架构设计

package main

import "fmt"

// 一个 Request 携带了基本的请求 a, b 以及用于回复客户端的 reply 管道
type Request struct {
a, b int
replyc chan int // reply channel inside the Request
}

type binOp func(a, b int) int

func run(op binOp, req *Request) {
req.replyc <- op(req.a, req.b)
}

func server(op binOp, service chan *Request, quit chan bool) {
for {
select {
case req := <-service:
go run(op, req)
case <-quit:
return
}
}
}

func startServer(op binOp) (service chan *Request, quit chan bool) {
service = make(chan *Request)
quit = make(chan bool)
go server(op, service, quit)
return service, quit
}

func main() {
adder, quit := startServer(func(a, b int) int { return a + b })
const N = 100
var reqs [N]Request

// 构建 100 个请求
for i := 0; i < N; i++ {
req := &reqs[i]
req.a = i
req.b = i + N
req.replyc = make(chan int)

// 把请求发送给 server
adder <- req
}

// checks:
for i := N - 1; i >= 0; i-- { // doesn't matter what order
if <-reqs[i].replyc != N+2*i {
fmt.Println("fail at", i)
} else {
fmt.Println("Request ", i, " is ok!")
}
}

quit <- true
fmt.Println("done")
}

限制同时处理的请求数

只需简单的修改:

// ...

const MAXREQS = 50

var sem = make(chan int, MAXREQS)

// ...

func process(op binOp, req *Request) int {
return op(req.a, req.b)
}

func handle(op binOp, r *Request) {
sem <- 1 // doesn't matter what we put in it
result := process(op, r)
<-sem // one empty place in the buffer: the next request can start
r.replyc <- result
}

func server(op binOp, service chan *Request, quit chan bool) {
for {
select {
case req := <-service:
go handle(op, req)
case <-quit:
return
}
}
}

// ...

超过 MAXREQS 的请求将不会被同时处理,因为当信号通道表示缓冲区已满时 handle 函数会阻塞且不再处理其他请求,直到某个请求从 sem 中被移除。

但是注意!! handle 方法一定要让 r.replyc <- result 放在下面,否则会造成信道死锁

References

Go语言学习——channel的死锁其实没那么复杂 14.10.1 典型的客户端/服务器(C/S)模式 14.11 限制同时处理的请求数